﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Runtime.InteropServices;
using System.Net;
using zlib;

namespace NETCommon.Communication
{
    public static class NSocket
    {

        static Socket server = null;
        public static bool ConnectServer(string IP, int Port, ref Socket MyServer)
        {
            IPEndPoint ipep = new IPEndPoint(
                            IPAddress.Parse(IP), Port);

            server = new Socket(AddressFamily.InterNetwork,
                           SocketType.Stream, ProtocolType.Tcp);


            try
            {
                server.Connect(ipep);
                MyServer = server;
                return true;
            }
            catch (SocketException e)
            {
                Console.WriteLine("Unable to connect to server.");
                Console.WriteLine(e.ToString());
                return false;
            }
        }
        public static byte[] Serialize(Object anything)
        {
            int rawsize = Marshal.SizeOf(anything);
            byte[] rawdatas = new byte[rawsize];
            Serialize(rawdatas, 0, anything);
            return rawdatas;
        }
        public static int Serialize(byte[] OutBuffer, int offset, Object anything)
        {
            int rawsize = Marshal.SizeOf(anything);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.StructureToPtr(anything, buffer, false);
            Marshal.Copy(buffer, OutBuffer, offset, rawsize);
            Marshal.FreeHGlobal(buffer);

            return rawsize;
        }
        public static int Serialize(Stream OutBuffer, Object anything)
        {
            List<Object> myList = anything as List<Object>;
            int ret = 0;
            if (myList == null)
            {
                byte[] ByteBuffer = Serialize(anything);
                OutBuffer.Write(ByteBuffer, 0, ByteBuffer.Length);

                ret = ByteBuffer.Length;
            }
            else
            {
                ret = Serialize(OutBuffer, myList);
            }

            return ret;
        }
        public static int Serialize(byte[] OutBuffer, int offset, List<Object> anything)
        {
            int rawsize = Marshal.SizeOf(anything);
            int ret = 0;
            for (int i = 0; i < anything.Count; i++)
            {
                ret += Serialize(OutBuffer, offset + i * rawsize, anything[i]);
            }

            return ret;
        }
        public static int Serialize(byte[] OutBuffer, int offset, params Object[] anything)
        {
            int rawsize = Marshal.SizeOf(anything);
            int ret = 0;
            for (int i = 0; i < anything.Length; i++)
            {
                List<Object> myList = anything[i] as List<Object>;
                if (myList == null)
                {
                    ret += Serialize(OutBuffer, offset + i * rawsize, anything[i]);
                }
                else
                {
                    ret += Serialize(OutBuffer, offset, myList);
                }
            }

            return ret;
        }
        public static int Serialize(Stream OutBuffer, List<Object> anything)
        {
            int ret = 0;
            foreach (var item in anything)
            {
                ret += Serialize(OutBuffer, item);
            }

            return ret;
        }
        public static int Serialize(Stream OutBuffer, params Object[] anything)
        {
            int ret = 0;
            for (int i = 0; i < anything.Length; i++)
            {
                List<Object> myList = anything[i] as List<Object>;
                if (myList == null)
                {
                    ret += Serialize(OutBuffer, anything[i]);
                }
                else
                {
                    ret += Serialize(OutBuffer, myList);
                }
            }

            return ret;
        }

        public static Object Deserialize(byte[] InBuffer, int offset, Type anytype)
        {
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > InBuffer.Length - offset)
                return null;
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(InBuffer, offset, buffer, rawsize);
            Object retobj = Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }
        public static Object Deserialize(byte[] rawdatas, Type anytype)
        {
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > rawdatas.Length)
                return null;
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(rawdatas, 0, buffer, rawsize);
            Object retobj = Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }
        public static Object Deserialize(Stream InBuffer, Type anytype)
        {
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > (int)InBuffer.Length)
                return null;
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            byte[] ByteBuffer = new byte[rawsize];
            ReadN(InBuffer, ByteBuffer, 0, rawsize);

            Marshal.Copy(ByteBuffer, 0, buffer, rawsize);
            Object retobj = Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }

        public static List<Object> Deserialize(byte[] InBuffer, int offset, Type anytype, int Count)
        {
            List<Object> retList = new List<Object>();
            int rawsize = Marshal.SizeOf(anytype);
            for (int i = 0; i < Count; i++)
            {
                retList.Add(Deserialize(InBuffer, offset + i * rawsize, anytype));
            }

            return retList;
        }
        public static List<Object> Deserialize(Stream InBuffer, Type anytype, int Count)
        {
            List<Object> retList = new List<Object>();
            if (Count == int.MaxValue)
            {
                int remain = (int)(InBuffer.Length - InBuffer.Position);
                int rawsize = Marshal.SizeOf(anytype);
                int itemcount = remain / rawsize;

                for (int i = 0; i < itemcount; i++)
                {
                    retList.Add(Deserialize(InBuffer, anytype));
                }
            }
            else
            {
                for (int i = 0; i < Count; i++)
                {
                    retList.Add(Deserialize(InBuffer, anytype));
                }
            }

            return retList;
        }

        public static T Deserialize<T>(byte[] InBuffer, int offset)
        {
            Type anytype = typeof(T);
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > InBuffer.Length - offset)
                return default(T);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(InBuffer, offset, buffer, rawsize);
            T retobj = (T)Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }
        public static T Deserialize<T>(byte[] rawdatas)
        {
            Type anytype = typeof(T);
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > rawdatas.Length)
                return default(T);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            Marshal.Copy(rawdatas, 0, buffer, rawsize);
            T retobj = (T)Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }
        public static T Deserialize<T>(Stream InBuffer)
        {
            Type anytype = typeof(T);
            int rawsize = Marshal.SizeOf(anytype);
            if (rawsize > (int)InBuffer.Length)
                return default(T);
            IntPtr buffer = Marshal.AllocHGlobal(rawsize);
            byte[] ByteBuffer = new byte[rawsize];
            ReadN(InBuffer, ByteBuffer, 0, rawsize);

            Marshal.Copy(ByteBuffer, 0, buffer, rawsize);
            T retobj = (T)Marshal.PtrToStructure(buffer, anytype);
            Marshal.FreeHGlobal(buffer);
            return retobj;
        }

        public static List<T> Deserialize<T>(byte[] InBuffer, int offset, int Count)
        {
            Type anytype = typeof(T);
            List<T> retList = new List<T>();
            int rawsize = Marshal.SizeOf(anytype);
            for (int i = 0; i < Count; i++)
            {
                retList.Add(Deserialize<T>(InBuffer, offset + i * rawsize));
            }

            return retList;
        }
        public static List<T> Deserialize<T>(Stream InBuffer, int Count)
        {
            Type anytype = typeof(T);

            List<T> retList = new List<T>();
            if (Count == int.MaxValue)
            {
                int remain = (int)(InBuffer.Length - InBuffer.Position);
                int rawsize = Marshal.SizeOf(anytype);
                int itemcount = remain / rawsize;

                for (int i = 0; i < itemcount; i++)
                {
                    retList.Add(Deserialize<T>(InBuffer));
                }
            }
            else
            {
                for (int i = 0; i < Count; i++)
                {
                    retList.Add(Deserialize<T>(InBuffer));
                }
            }

            return retList;
        }

        public static byte[] CompressCache(Object myReq)
        {
            byte[] cacheBytes = Serialize(myReq);

            // small arrays almost never yeild a benefit from compressing
            if (cacheBytes.Length < 50)
                return cacheBytes;

            MemoryStream outMsg = new MemoryStream();
            MemoryStream compressedBuffer = new MemoryStream();
            ZOutputStream zos = new ZOutputStream(compressedBuffer, zlibConst.Z_DEFAULT_COMPRESSION);
            zos.Write(cacheBytes, 0, (int)cacheBytes.Length);
            zos.finish();
            zos.Close();

            // if the compression hasn't helped, then just return null
            if (compressedBuffer.Length >= cacheBytes.Length)
            {
                // Write original to outMsg
                outMsg.Write(cacheBytes, 0, cacheBytes.Length);
            }
            else
            {
                // Write compressed to outMsg
                compressedBuffer.WriteTo(outMsg);
            }

            return outMsg.ToArray();
        }
        public static byte[] UnCompressCache(byte[] cacheBodyBytes, UInt32 BodyLength)
        {
            MemoryStream oInStream = new MemoryStream(cacheBodyBytes, 0, Convert.ToInt32(BodyLength));
            ZInputStream oZInstream = new ZInputStream(oInStream);
            MemoryStream oOutStream = new MemoryStream();

            byte[] buffer = new byte[2000];
            int len;
            while ((len = oZInstream.read(buffer, 0, 2000)) > 0)
            {
                oOutStream.Write(buffer, 0, len);
            }
            oOutStream.Flush();
            byte[] arrUncompressed = oOutStream.ToArray();
            oZInstream.Close();
            oOutStream.Close();

            return arrUncompressed;
        }

        public static byte[] GetVariantFromValue(Object value)
        {
            return BitConverter.GetBytes(Convert.ToUInt64(value));
        }
        public static Object GetValueFromVariant(byte[] value, VarEnum VarType)
        {
            Object myObj;

            switch (VarType)
            {
                case VarEnum.VT_BOOL:
                    myObj = value[0];
                    break;
                case VarEnum.VT_I1:
                    myObj = Convert.ToSByte(value[0]);
                    break;
                case VarEnum.VT_I2:
                    myObj = BitConverter.ToInt16(value, 0);
                    break;
                case VarEnum.VT_INT:
                case VarEnum.VT_I4:
                    myObj = BitConverter.ToInt32(value, 0);
                    break;
                case VarEnum.VT_I8:
                    myObj = BitConverter.ToInt64(value, 0);
                    break;
                case VarEnum.VT_R4:
                    myObj = BitConverter.ToSingle(value, 0);
                    break;
                case VarEnum.VT_R8:
                    myObj = BitConverter.ToDouble(value, 0);
                    break;
                case VarEnum.VT_UI1:
                    myObj = value[0];
                    break;
                case VarEnum.VT_UI2:
                    myObj = BitConverter.ToUInt16(value, 0);
                    break;
                case VarEnum.VT_UINT:
                case VarEnum.VT_UI4:
                    myObj = BitConverter.ToUInt32(value, 0);
                    break;
                case VarEnum.VT_UI8:
                    myObj = BitConverter.ToUInt64(value, 0);
                    break;
                default:
                    myObj = null;
                    break;
            }
            return myObj;
        }

        public static void ReadN(Stream ns, byte[] buffer, int Offset, int length)
        {
            if (!ns.CanRead) return;
            int remain = length;
            int intRead = 0;

            while ((remain > 0) && ns.CanRead)
            {
                try
                {
                    intRead = ns.Read(buffer, Offset, remain);
                }
                catch (Exception)
                {
                    remain = 0;
                }
                Offset += intRead;
                remain -= intRead;
            }

        }
        public static void WriteN(Stream ns, byte[] buffer, int Offset, int length)
        {
            if (!ns.CanWrite) return;
            ns.Write(buffer, Offset, length);
        }
    }
}
